/*
 * Copyright (c) 2020, HiHope Community.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "ui_core.h"

#include <stdlib.h>

#include "ui_hal.h"

#define US_PER_S (1000*1000)
#define MAX_FRAME_RATE   10
#define UI_HISTORY_SZIE  8

typedef struct {
    uint32_t targetFps;
    uint32_t frameCost;

    UI* leave;
    UI* enter;
    UI* current;
    uint32_t terminate;

    int32_t top; // pointed to the stack top
    UI* stack[UI_HISTORY_SZIE];
} UICoreState;

static volatile UICoreState gState = {
    MAX_FRAME_RATE, // targetFps
    0,              // frameCost
    NULL,           // leave
    NULL,           // enter
    NULL,           // current
    0,              // terminate
    -1,             // top
    {0}             // stack
};

uint32_t UIGetCurrentFrameCostUs()
{
    return gState.frameCost;
}

float UIGetCurrentFrameRate(void)
{
    if (gState.frameCost > 0) {
        return US_PER_S / (float)gState.frameCost;
    }
    return (float) gState.targetFps;
}

Status UISetFrameRate(unsigned fps)
{
    if (fps > MAX_FRAME_RATE) {
        return UI_FAIL;
    }
    gState.targetFps = fps;
    return UI_OK;
}

Status UIBack(void)
{
    if (gState.top < 0) {
        return UI_FAIL;
    }

    // pop last ui from the history stack
    UI* ui = gState.stack[gState.top--];
    LOGD("pop %p from stack, top: %d", ui, gState.top);
    if (ui == gState.current) { // top is current, pop again
        ui = gState.stack[gState.top--];
        LOGD("pop %p from stack, top: %d", ui, gState.top);
    }

    UISwitch(ui);
    return UI_OK;
}

Status UISwitch(UI* ui)
{
    gState.leave = gState.current;
    gState.current = ui;
    gState.enter = ui;

    // check history stack size
    if (gState.top >= UI_HISTORY_SZIE) {
        return UI_FAIL;
    }

    // push ui to history stack
    gState.top++;
    gState.stack[gState.top] = ui;
    LOGD("push %p to stack, top: %d", ui, gState.top);

    return UI_OK;
}

uint32_t UIRun(UI* ui)
{
    UISwitch(ui);
    uint32_t tsLast = HalGetCurrentTimeUs();
    while (!gState.terminate) {
        // leave the old ui
        UI* leaveUI = gState.leave;
        if (leaveUI && leaveUI->onLeave) {
            leaveUI->onLeave(leaveUI);
            gState.leave = NULL;
        }

        // enter new ui
        UI* enterUI = gState.enter;
        if (enterUI && enterUI->onEnter) {
            enterUI->onEnter(enterUI);
            gState.enter = NULL;
        }

        // get key event
        KeyEvent keyEvent = HalGetKeyEvent();

        // draw ui
        UI* currentUI = gState.current;
        if (currentUI && currentUI->onDraw) {
            currentUI->onDraw(currentUI);
        }

        // handle key event
        if (keyEvent.key != K_NONE && currentUI && currentUI->onKey) {
            currentUI->onKey(currentUI, keyEvent);
        }

        uint32_t tsCurrent = HalGetCurrentTimeUs();
        uint32_t actualUs = tsCurrent - tsLast;
        uint32_t expectUs = US_PER_S / gState.targetFps;
        if (actualUs < expectUs) {
            HalDelayUs(expectUs - actualUs);
        }

        gState.frameCost = actualUs;
        tsLast = tsCurrent;
    }
    return UI_OK;
}

Status UITerminate()
{
    if (gState.terminate) {
        return UI_FAIL;
    }
    gState.terminate = 1;
    return UI_OK;
}

UI* UICreate(void* ctx,
            const char* name,
            UIDrawCallback onDraw,
            UIEnterCallback onEnter,
            UILeaveCallback onLeave,
            UIEventCallback onKey,
            UICreateCallback onCreate,
            UIDestroyCallback onDestroy)
{
    UI* ui = (UI*) malloc(sizeof(UI));
    if (ui == NULL) {
        return NULL;
    }

    ui->ctx = ctx;
    ui->name = name;
    ui->onDraw = onDraw;
    ui->onEnter = onEnter;
    ui->onLeave = onLeave;
    ui->onKey = onKey;
    ui->onDestroy = onDestroy;

    if (onCreate) {
        onCreate(ui);
    }
    return ui;
}

Status UIDestroy(UI* ui)
{
    if (ui == NULL) {
        return UI_FAIL;
    }

    if (ui->onDestroy) {
        ui->onDestroy(ui);
    }
    free(ui);
    return UI_OK;
}
